/*
 * Copyright Aristool AG Switzerland
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */
package org.opprua.tidy.gens;

import java.util.ArrayList;
import java.util.List;

import org.opprua.tidy.util.CodePrint;
import org.opprua.tidy.util.Primitives;
import org.opprua.tidy.util.StringUtil;

/**
 * TODO: add Java doc
 * 
 * Prepared for first commit: 15.04.2007
 * 
 * @author Lin Li
 */
public class GBean extends GClass {

  private boolean liveLists = true;
  private List<GVar> fields = new ArrayList<GVar>();

  public GBean(String name) {
    super(name);
    comment("Bean class generated by tidy bean generator: " + name + ".\n<p>");
  }

  public void disableLiveLists() {
    liveLists = false;
  }

  public void beanField(String type, String name, boolean isArray) {
    beanField(type, name, null, null, null, isArray);
  }

  public void beanField(String type, String name, String init, boolean isArray) {
    beanField(type, name, init, null, null, isArray);
  }

  public void beanField(String type, String name, String init, String doc,
              String column, boolean isArray) {
    if (isArray && !liveLists) type += "[]";
    GVar v = new GVar("private", type, name, init);
    if (isArray && liveLists) v.setArrayType();
    v.comment(doc);
    beanField(v);
  }

  public void beanField(GVar var) {
    fields.add(var);
  }

  private void writeComments() {

    comment("\n<p><b>List of fields in this bean:</b></p>");
    comment("<table cellpadding='5' width='95%'>\n");
    comment("<tr bgcolor='#b2cfc4'><td>Name</td><td>Type</td><td>Description</td></tr>\n");

    for (int i = 0; i < fields.size(); i++) {
      GVar v = fields.get(i);
      comment("<tr><td valign='top'><b>");
      comment(v.getName());
      comment("</b></td><td valign='top'>");
      comment(v.getType());
      comment("</td><td valign='top'>");
      String doc = v.clearComment();
      if (doc != null && doc.trim().length() > 0) {
        comment(doc);
      } else {
        comment("No comments");
      }
      comment("</td></tr>");
    }
    comment("</table>");
  }

  private String findInit(String type, boolean isArray) {
    if (isArray) {
      return "new " + type + "[0]";
    } else if (type.endsWith("[]")) {
      return "new " + type.substring(0, type.length() - 2) + "[0]";
    } else if (Primitives.getFor(type) != null) {
      return Primitives.getFor(type).getInitializer();
    }
    return "new " + type + "()";
  }

  private void writeConstructors() {
    String name = getClassName();

    GMethod def = newMethod("public", name, "");
    def.addStatement("clean()");
    def.comment("Default constructor, all fields "
                + "initialized with default value");

    GMethod assignCtr = newMethod("public", getClassName(), "");
    assignCtr.comment("Assignment constructor, with every field "
                + "initialized by the value provided");
    for (int i = 0; i < fields.size(); i++) {
      GVar v = fields.get(i);
      assignCtr.addParameter(v.getExternalType() + (v.isArray() ? "[]" : ""), v
                  .getName());
      assignCtr.addStatement("set" + v.getCName() + "(" + v.getName() + ")");
    }

    def = newMethod("public", name, "");
    def.addParameter(name, "data");
    def.comment("Clone constructor, all fields reference copied");
    for (int i = 0; i < fields.size(); i++) {
      GVar v = fields.get(i);
      def.addStatement("this.set" + v.getCName() + "(data.get" + v.getCName()
                  + "())");
    }

    def = newMethod("public", "clean", "void");
    def.comment("Reset method, all fields reset to default value");
    for (int i = 0; i < fields.size(); i++) {
      GVar v = fields.get(i);
      String init = v.getExInit();
      if (init == null || init.length() == 0) {
        init = findInit(v.getExternalType(), v.isArray());
      }
      def.addStatement("this.set" + v.getCName() + "(" + init + ")");
    }

    GMethod c = newMethod("public", "clone", "Object");
    c.comment("Clone, overrides the one from Object");
    c.addStatement("return new " + name + "(this)");
  }

  private static String nameField(String name) {
    StringBuffer b = new StringBuffer("FDN_");
    int j = 0;
    for (int i = 0; i < name.length(); i++) {
      if (Character.isUpperCase(name.charAt(i))) {
        b.append(name.substring(j, i).toUpperCase());
        b.append("_");
        j = i;
      }
    }
    b.append(name.substring(j).toUpperCase());
    return b.toString();
  }

  private void writeSummaries() {
    StringBuffer f = new StringBuffer();
    for (int i = 0; i < fields.size(); i++) {
      addField(fields.get(i));
    }
    for (int i = 0; i < fields.size(); i++) {
      GVar v = fields.get(i);
      String fname = nameField(v.getName());
      addField("public static final", "String", fname, "\"" + v.getName()
                  + "\"");
      if (f.length() > 0) {
        f.append(",\n            ");
      }
      f.append(fname);
    }
    addField("public static final", "String[]", "fieldList", "{" + f.toString()
                + "}");

    newMethod("public", "listNames", "String[]").addStatement(
                "return fieldList");

    GIf get = new GIf();
    GIf set = new GIf();
    GIf typeOf = new GIf();
    for (int i = 0; i < fields.size(); i++) {
      GVar v = fields.get(i);
      String c = v.getExternalType();
      Primitives<?> p = Primitives.getFor(c);
      if (p != null) c = p.getWrapperName();
      if (v.isArray()) c += "[]";
      get.addCondition("name.equals(\"" + v.getName() + "\")").addStatement(
                  "return get" + v.getCName() + "()");
      set.addCondition("name.equals(\"" + v.getName() + "\")").addStatement(
                  "set" + v.getCName() + "((" + c + ")value)");
      typeOf.addCondition("name.equals(\"" + v.getName() + "\")").addStatement(
                  "return " + c + ".class");
    }
    GMethod getm = newMethod("public", "getter", "Object");
    getm.addParameter("String", "name");
    getm.addStatement(get);
    getm.addStatement("return null");

    GMethod typeOfm = newMethod("public", "typeOf", "Class<?>");
    typeOfm.addParameter("String", "name");
    typeOfm.addStatement(typeOf);
    typeOfm.addStatement("return null");

    GMethod setm = newMethod("public", "setter", "void");
    setm.addParameter("String", "name");
    setm.addParameter("Object", "value");
    setm.addStatement(set);
  }

  private String commentGetter(GVar v) {
    StringBuffer buf = new StringBuffer();
    buf.append("Getter for field ").append(v.getName()).append(".\n");
    buf.append("\n@return ").append(v.getType()).append(" value of the field ")
                .append(v.getName());
    return buf.toString();
  }

  private String commentSetter(GVar v) {
    StringBuffer buf = new StringBuffer();
    buf.append("Setter for field ").append(v.getName()).append("\n");
    buf.append("\n").append("@param a").append(v.getCName()).append(
                " new value of the field ").append(v.getName());
    return buf.toString();
  }

  private void writeSingle(GVar v) {

    GMethod getter = newMethod("public", "get" + v.getCName(), v
                .getExternalType());
    GMethod setter = newMethod("public", "set" + v.getCName(), "void");
    setter.addParameter(v.getExternalType(), "a" + v.getCName());
    getter.comment(commentGetter(v));
    getter.annotate(v.getGetterAnnotation());
    setter.comment(commentSetter(v));
    setter.annotate(v.getSetterAnnotation());

    getter.addStatement("return " + v.getName());
    setter.addStatement(v.getName() + " = a" + v.getCName());

    boolean isArray = v.getExternalType().endsWith("[]");

    if (v.getType().equals("String")) {
      if (isArray) {
        addIntArrayConversion(v.getName());
      } else {
        addIntConversion(v.getName());
      }
    }

    if (isArray) {
      GMethod tester = newMethod("public", "get" + v.getCName() + "IsEmpty",
                  "boolean");
      tester.addStatement("return " + v.getName() + " == null || "
                  + v.getName() + ".length == 0");
      GMethod single = newMethod("public", "set" + v.getCName(), "void");

      single.addParameter(v.getType().substring(0, v.getType().length() - 2),
                  "a" + v.getCName());
      single.addStatement(v.getName() + " = new " + v.getType() + " {a"
                  + v.getCName() + "}");

      StringBuffer buf = new StringBuffer();
      buf.append("Convenience method to set a single element array for field ")
                  .append(v.getName()).append("\n");
      buf.append("\n").append("@param a").append(v.getCName());
      buf.append(" the only element in the array as ");
      buf.append("the new value of the field ").append(v.getName());
      single.comment(buf.toString());
    } else if (v.getType().equals("String")) {
      GMethod tester = newMethod("public", "get" + v.getCName() + "IsEmpty",
                  "boolean");
      tester.addStatement("return " + v.getName() + " == null || "
                  + v.getName() + ".length() == 0");
    }
  }

  private void writeArray(GVar v) {

    GMethod tester = newMethod("public", "get" + v.getCName() + "IsEmpty",
                "boolean");
    tester.addStatement("return " + v.getName() + " == null || " + v.getName()
                + ".size() == 0");

    GMethod getter = newMethod("public", "get" + v.getCName(), v
                .getExternalType()
                + "[]");
    getter.annotate(v.getGetterAnnotation());
    GMethod setter = newMethod("public", "set" + v.getCName(), "void");
    setter.addParameter(v.getExternalType() + "[]", "a" + v.getCName());
    setter.annotate(v.getSetterAnnotation());

    StringBuffer buf = new StringBuffer();
    buf.append("Getter for field ").append(v.getName()).append(".\n");
    buf.append("\n").append("@return ").append(v.getType()).append(
                " value of the field ").append(v.getName());
    getter.comment(buf.toString());

    buf = new StringBuffer();
    buf.append("Setter for field ").append(v.getName()).append("\n");
    buf.append("\n").append("@param a").append(v.getCName()).append(
                " new value of the field ").append(v.getName());
    setter.comment(buf.toString());

    getter.addStatement("return " + v.getName() + ".toArray(new "
                + v.getExternalType() + "[0])");
    setter.addStatement(v.getName() + ".clear()");
    GIf ig = new GIf();
    setter.addStatement(ig);
    ig.addCondition("a" + v.getCName() + " == null", v.getName()
                + " = java.util.Collections.synchronizedList"
                + "(new java.util.ArrayList<" + v.getExternalType() + ">())");
    ig.addCondition("", v.getName() + ".addAll(java.util.Arrays.asList(a"
                + v.getCName() + "))");

    GMethod adder = newMethod("public", "add" + v.getCName() + "Item", "int");
    adder.addParameter(v.getExternalType(), "a" + v.getCName());
    adder.addStatement(v.getName() + ".add(a" + v.getCName() + ")");
    adder.addStatement("return " + v.getName() + ".size()");

    buf = new StringBuffer();
    buf.append("Convenience method to add an item to the field ").append(
                v.getName()).append("\n");
    buf.append("\n").append("@param a").append(v.getCName()).append(
                " the new element to be added to the end of the array ")
                .append(v.getName());
    adder.comment(buf.toString());

    GMethod fetcher = newMethod("public", "get" + v.getCName() + "Item", v
                .getExternalType());
    fetcher.addParameter("int", "index");
    fetcher.addStatement("return (" + v.getExternalType() + ")" + v.getName()
                + ".get(index)");

    buf = new StringBuffer();
    buf.append("Convenience method to fetch an item from the field ").append(
                v.getName()).append("\n");
    buf.append("\n@param index the index of the element to fetch\n");
    buf.append("@return ").append(v.getExternalType()).append(
                " the element at index");
    fetcher.comment(buf.toString());

    GMethod size = newMethod("public", "getSizeOf" + v.getCName(), "int");
    size.addStatement("return " + v.getName() + ".size()");
    if (v.getExternalType().equals("String")) {
      addIntListConversion(v.getName());
    }

    buf = new StringBuffer();
    buf.append("Convenience method to get the" + " array size of the field ");
    buf.append(v.getName());
    buf.append(" (getting the array and then the size costs a bit more)\n");
    buf.append("@return ").append(v.getExternalType());
    buf.append(" the array size of the filed ").append(v.getName());
    size.comment(buf.toString());

    GMethod single = newMethod("public", "set" + v.getCName(), "void");

    single.addParameter(v.getExternalType(), "a" + v.getCName());
    single.addStatement(v.getName() + ".clear()");
    single.addStatement(v.getName() + ".add(a" + v.getCName() + ")");

    buf = new StringBuffer();
    buf.append("Convenience method to set a single element array for field ")
                .append(v.getName()).append("\n");
    buf.append("\n").append("@param a").append(v.getCName());
    buf.append(" the only element in the array as the new value of the field ");
    buf.append(v.getName());
    single.comment(buf.toString());
  }

  private void addIntConversion(String name) {
    String cname = StringUtil.capFirst(name);

    GMethod ai = newMethod("public", "get" + cname + "AsInt", "int");
    GIf length = new GIf();
    length.addCondition(name + ".trim().length() == 0", "return defValue");
    GTry parse = new GTry();
    parse.addWorkStatement("return Integer.parseInt(" + name + ")");
    parse.newProcessing("Throwable", "t").addStatement("return defValue");
    ai.addStatement(length);
    ai.addStatement(parse);
    ai.addParameter("int", "defValue");

    StringBuffer buf = new StringBuffer();
    buf.append("Convenience method to convert the string field ");
    buf.append(name).append(" to an int\n");
    buf.append("\n@param defValue result when the conversion fails\n");
    buf.append("@return int the converted result or defValue");
    ai.comment(buf.toString());

    ai = newMethod("public", "get" + cname + "AsInt", "int");
    ai.addStatement("return get" + cname + "AsInt(0)");

    buf = new StringBuffer();
    buf.append("Convenience method to convert the string field ");
    buf.append(name).append(
                " to an int if the conversion fails, the result will be 0.\n");
    buf.append("\n").append("@return int the converted result or 0");
    ai.comment(buf.toString());
  }

  private void addIntArrayConversion(String name) {
    String cname = StringUtil.capFirst(name);

    GMethod ai = newMethod("public", "get" + cname + "AsIntArray", "int[]");
    ai.addStatement("int[] res = new int[" + name + ".length]");
    GState loop = new GState();
    loop.setHead("for(int i = 0; i < res.length; ++i)");
    GIf length = new GIf();
    length.addCondition(name + "[i].trim().length() == 0", "res[i] = defValue");
    GTry parse = new GTry();
    parse.addWorkStatement("res[i] = Integer.parseInt(" + name + "[i])");
    parse.newProcessing("Throwable", "t").addStatement("res[i] = defValue");
    loop.addStatement(length);
    loop.addStatement(parse);

    ai.addStatement(loop);
    ai.addStatement("return res");
    ai.addParameter("int", "defValue");

    StringBuffer buf = new StringBuffer();
    buf.append("Convenience method to convert all strings in the field ");
    buf.append(name).append(" to an array of ints\n");
    buf.append("\n").append("@param defValue values in the result array ");
    buf.append("when the conversion fails\n");
    buf.append("@return int[] the converted results or defValues");
    ai.comment(buf.toString());

    ai = newMethod("public", "get" + cname + "AsIntArray", "int[]");
    ai.addStatement("return get" + cname + "AsIntArray(0)");

    buf = new StringBuffer();
    buf.append("Convenience method to convert all strings in the field ");
    buf.append(name).append(" to an array of ints, if anything fails, the ");
    buf.append("result will contain 0's\n");
    buf.append("\n").append("@return int[] the converted results or 0s");
    ai.comment(buf.toString());
  }

  private void addIntListConversion(String name) {
    String cname = StringUtil.capFirst(name);

    GMethod ai = newMethod("public", "get" + cname + "AsIntArray", "int[]");
    ai.addStatement("int[] res = new int[" + name + ".size()]");
    GState loop = new GState();
    loop.setHead("for(int i = 0; i < res.length; ++i)");
    loop.addStatement("String v = (String)" + name + ".get(i)");
    GIf length = new GIf();
    length.addCondition("v.trim().length() == 0", "res[i] = defValue");
    GTry parse = new GTry();
    parse.addWorkStatement("res[i] = Integer.parseInt(v)");
    parse.newProcessing("Throwable", "t").addStatement("res[i] = defValue");
    loop.addStatement(length);
    loop.addStatement(parse);

    ai.addStatement(loop);
    ai.addStatement("return res");
    ai.addParameter("int", "defValue");

    StringBuffer buf = new StringBuffer();
    buf.append("Convenience method to convert all strings in the field ");
    buf.append(name).append(" to an array of ints\n");
    buf.append("\n");
    buf.append("@param defValue values in the result ");
    buf.append("array when the conversion fails\n");
    buf.append("@return int[] the converted results or defValues");
    ai.comment(buf.toString());

    ai = newMethod("public", "get" + cname + "AsIntArray", "int[]");
    ai.addStatement("return get" + cname + "AsIntArray(0)");

    buf = new StringBuffer();
    buf.append("Convenience method to convert all strings in the field ");
    buf.append(name);
    buf.append(" to an array of ints, ");
    buf.append("if anything fails, the result will contain 0's\n");
    buf.append("\n").append("@return int[] the converted results or 0s");
    ai.comment(buf.toString());
  }

  public void dump(CodePrint w) {
    writeComments();
    writeConstructors();
    for (int i = 0; i < fields.size(); i++) {
      GVar v = fields.get(i);
      if (v.isArray()) writeArray(v);
      else writeSingle(v);
    }
    writeSummaries();
    super.dump(w);
  }
}
